import java.util.ArrayList;
import java.util.List;

public class Vector {
	
	private ArrayList<Double> my_values;
	protected double x = 0;
	protected double y = 0;
	protected double z = 0;
	protected double w = 0;
	
	/**
	 * Creates a four dimension zero vector.
	 */
	public Vector() {
		my_values = new ArrayList<Double>();
		for (int i = 0; i < 4; i++) my_values.add(0.0);
		x = my_values.get(0);
		y = my_values.get(1);
		z = my_values.get(2);
		w = my_values.get(3);
	}
	
	/**
	 * Creates a vector from a list of doubles.  Vectors with less than 4 dimensions will 
	 * not be able to use the protected x/y/z/w fields without issue.
	 * 
	 * @param args the list of doubles.
	 */
	public Vector(List<Double> args) {
		my_values = new ArrayList<Double>();
		my_values.addAll(args);
		for(int i = 0; i < my_values.size(); i++) {
			if(i == 0) x = my_values.get(0);
			if(i == 1) y = my_values.get(1);
			if(i == 2) z = my_values.get(2);
			if(i == 3) w = my_values.get(3);
		}
	}
	
	/**
	 * Creates a 4D vector from a series of doubles.
	 * 
	 * @param a first dimension.
	 * @param b second dimension.
	 * @param c third dimension.
	 * @param d fourth dimension.
	 */
	public Vector(double a, double b, double c, double d) {
		my_values = new ArrayList<Double>();
		my_values.add(a); x = a;
		my_values.add(b); y = b;
		my_values.add(c); z = c;
		my_values.add(d); w = d;
		
	}
	
	/**
	 * Returns the ith dimension value in a vector.
	 * 
	 * @param i the index dimension.
	 * @return the double value of the ith dimension.
	 */
	protected double getValue(int i) {
		return my_values.get(i);
	}
	
	/**
	 * Returns the dimension of the vector.
	 * 
	 * @return number of dimensions in the vector.
	 */
	protected int getDimension() {
		return my_values.size();
	}
	
	/**
	 * Performs dot product calculation of two vectors.
	 * 
	 * @throws IllegalArgumentException if vectors have different dimensions.
	 * @param v1 the first vector.
	 * @param v2 the second vector.
	 * @return the dot product of the two vectors.
	 */
	public static double product(Vector v1, Vector v2) {
		double value = 0.0;
		if(v1.getDimension() != v2.getDimension()) {
			throw new IllegalArgumentException("Vectors must have the same length");
		}
		for(int i = 0; i < v1.getDimension(); i++) {
			value += v1.getValue(i) * v2.getValue(i);
		}
		return value;
	}
	
	/**
	 * Instance dot product calculation of current vector and one other vector.
	 * Uses the static dot product calculation.
	 * 
	 * @throws IllegalArgumentException if vectors have different dimensions.
	 * @param v the other vector in the dot product.
	 * @return the dot product.
	 */
	public double product(Vector v) {
		return product(this, v);
	}
	
	public Vector plus(Vector v) {
		return new Vector(this.x + v.x, this.y + v.y, this.z + v.z, this.w + v.w);
	}
	
	public Vector minus(Vector v) {
		return new Vector(this.x - v.x, this.y - v.y, this.z - v.z, this.w - v.w);
	}
	
	public static Vector normalize(Vector v) {
		return v.scale(1.0 / Math.sqrt(v.product(v)));
	}
	
	/**
	 * Cross product computation for two 4D vectors, (x, y, z, w), where w is a place holder.
	 * 
	 * @throws IllegalArgumentException if either vector is not the correct size.
	 * @param a first vector.
	 * @param b second vector.
	 * @return resultant vector.
	 */
	public static Vector cross(Vector a, Vector b) {
		if(a.getDimension() != 4 || b.getDimension() != 4)
			throw new IllegalArgumentException("Cross product vectors must be 4D (x, y, z, w)");
		List<Double> values = new ArrayList<Double>();
		values.add(a.getValue(1) * b.getValue(2) - a.getValue(2) * b.getValue(1));
		values.add(a.getValue(2) * b.getValue(0) - a.getValue(0) * b.getValue(2));
		values.add(a.getValue(0) * b.getValue(1) - a.getValue(1) * b.getValue(0));
		values.add(0.0);
		return new Vector(values);
	}

	/**
	 * Cross product computation for two 4D vectors, (x, y, z, w), where w is a place holder.
	 * 
	 * @throws IllegalArgumentException if either vector is not the correct size.
	 * @param b second vector.
	 * @return resultant vector.
	 */
	public Vector cross(Vector b) {
		return cross(this, b);
	}
	
	/**
	 * Scales this vector.
	 * 
	 * @param s the scalar.
	 * @return the scaled vector.
	 */
	public Vector scale(double s) {
		ArrayList<Double> values = new ArrayList<Double>();
		for(int i = 0; i < this.getDimension(); i++) {
			values.add(this.getValue(i) * s);
		}
		return new Vector(values);
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("(");
		for(double d : this.my_values) {
			sb.append(String.format("\t%.4f", d));
		}
		sb.append("  )");
		return sb.toString();
	}
	
	public boolean equals(Vector other) {
		boolean value = true;
		if(this.getDimension() != other.getDimension()) value = false;
		else {
			for(int i = 0; i < this.getDimension(); i++) {
				if(this.getValue(i) != other.getValue(i)) value = false;
			}
		}
		return value;
	}
	
	@Override
	public int hashCode() {
		return this.toString().hashCode();
	}
}
